File: AbstractCreateTestAccessor`1.cs
Web Access
Project: src\src\RoslynAnalyzers\Roslyn.Diagnostics.Analyzers\Core\Roslyn.Diagnostics.Analyzers.csproj (Roslyn.Diagnostics.Analyzers)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable warnings
 
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Analyzer.Utilities;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Text;
 
namespace Roslyn.Diagnostics.Analyzers
{
    public abstract class AbstractCreateTestAccessor<TTypeDeclarationSyntax> : CodeRefactoringProvider
        where TTypeDeclarationSyntax : SyntaxNode
    {
        protected AbstractCreateTestAccessor()
        {
        }
 
        private protected abstract IRefactoringHelpers RefactoringHelpers { get; }
 
        protected abstract SyntaxNode GetTypeDeclarationForNode(SyntaxNode reportedNode);
 
        public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
        {
            var type = await context.TryGetRelevantNodeAsync<TTypeDeclarationSyntax>(RefactoringHelpers).ConfigureAwait(false);
            if (type is null)
                return;
 
            var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
            var typeSymbol = (INamedTypeSymbol)semanticModel.GetDeclaredSymbol(type, context.CancellationToken);
            if (!IsClassOrStruct(typeSymbol))
                return;
 
            if (typeSymbol.GetTypeMembers(TestAccessorHelper.TestAccessorTypeName).Any())
                return;
 
            var location = typeSymbol.Locations.FirstOrDefault(location => location.IsInSource && Equals(location.SourceTree, semanticModel.SyntaxTree));
            if (location is null)
                return;
 
            context.RegisterRefactoring(
                CodeAction.Create(
                    RoslynDiagnosticsAnalyzersResources.CreateTestAccessorMessage,
                    cancellationToken => CreateTestAccessorAsync(context.Document, location.SourceSpan, cancellationToken),
                    nameof(AbstractCreateTestAccessor<TTypeDeclarationSyntax>)));
        }
 
        private static bool IsClassOrStruct(ITypeSymbol typeSymbol)
            => typeSymbol.TypeKind is TypeKind.Class or TypeKind.Struct;
 
        private async Task<Document> CreateTestAccessorAsync(Document document, TextSpan sourceSpan, CancellationToken cancellationToken)
        {
            var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
 
            var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
            var syntaxRoot = await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
            var reportedNode = syntaxRoot.FindNode(sourceSpan, getInnermostNodeForTie: true);
            var typeDeclaration = GetTypeDeclarationForNode(reportedNode);
            var type = (ITypeSymbol)semanticModel.GetDeclaredSymbol(typeDeclaration, cancellationToken);
 
            var syntaxGenerator = SyntaxGenerator.GetGenerator(document);
            var newTestAccessorExpression = syntaxGenerator.ObjectCreationExpression(
                syntaxGenerator.IdentifierName(TestAccessorHelper.TestAccessorTypeName),
                syntaxGenerator.ThisExpression());
            var getTestAccessorMethod = syntaxGenerator.MethodDeclaration(
                TestAccessorHelper.GetTestAccessorMethodName,
                returnType: syntaxGenerator.IdentifierName(TestAccessorHelper.TestAccessorTypeName),
                accessibility: Accessibility.Internal,
                statements: new[] { syntaxGenerator.ReturnStatement(newTestAccessorExpression) });
 
            var parameterName = "instance";
            var fieldName = "_" + parameterName;
            var testAccessorField = syntaxGenerator.FieldDeclaration(
                fieldName,
                syntaxGenerator.TypeExpression(type),
                Accessibility.Private,
                DeclarationModifiers.ReadOnly);
            var testAccessorConstructor = syntaxGenerator.ConstructorDeclaration(
                containingTypeName: TestAccessorHelper.TestAccessorTypeName,
                parameters: new[] { syntaxGenerator.ParameterDeclaration(parameterName, syntaxGenerator.TypeExpression(type)) },
                accessibility: Accessibility.Internal,
                statements: new[] { syntaxGenerator.AssignmentStatement(syntaxGenerator.IdentifierName(fieldName), syntaxGenerator.IdentifierName(parameterName)) });
            var testAccessorType = syntaxGenerator.StructDeclaration(
                TestAccessorHelper.TestAccessorTypeName,
                accessibility: Accessibility.Internal,
                modifiers: DeclarationModifiers.ReadOnly,
                members: new[] { testAccessorField, testAccessorConstructor });
 
            var newTypeDeclaration = syntaxGenerator.AddMembers(typeDeclaration, getTestAccessorMethod, testAccessorType);
            return document.WithSyntaxRoot(syntaxRoot.ReplaceNode(typeDeclaration, newTypeDeclaration));
        }
    }
}